package server

import (
	"context"
	"encoding/hex"
	"net"
	"strconv"
	"time"

	"gitee.com/crack007/goose/gsjob"
	"gitee.com/crack007/goose/gslog"
	"gitee.com/crack007/goose/gsutil"
)

type Client struct {
	conn                    net.Conn
	Host                    string
	Port                    uint16
	network                 string
	writeTimeout            uint
	onMessage               func(client *Client, data []byte)
	readBuffer              uint
	readMsgQueue            chan []byte
	readMsgQueueSize        uint
	writeMsgQueue           chan []byte
	writeMsgQueueSize       uint
	ctx                     context.Context
	cancel                  context.CancelFunc
	readIdleJob             *ClientJob
	writeIdleJob            *ClientJob
	idleJob                 *ClientJob
	readIdleTimeout         time.Duration // 读空闲超时时间。
	writeIdleTimeout        time.Duration // 写空闲超时时间。
	idleTimeout             time.Duration // 空闲超时时间。
	lastReadAt              time.Time
	lastWriteAt             time.Time
	lastActiveAt            time.Time
	readIdleTimeoutHandler  func(client *Client)
	writeIdleTimeoutHandler func(client *Client)
	idleTimeoutHandler      func(client *Client)
	connectHandler          func(client *Client)
	errorHandler            func(client *Client, err error)
	reconnectInterval       uint
	reconnectTimer          *time.Timer // 断线重连时钟
	isUnix                  bool
	reConnect               bool
	id                      uint64
	closeHandler            func(client *Client)
	asyncSend               bool
}

func (x *Client) connect() (err error) {
	remote := x.Host + ":" + strconv.Itoa(int(x.Port))
	if x.isUnix {
		remote = x.Host
	}

	x.conn, err = net.Dial(x.network, remote)
	if err != nil {
		gslog.Debug("[%d] Client session connect failed, network: %s, remote: %s, err:%v", x.id, x.network, remote, err)
		return err
	}

	ip, port := gsutil.IpUtil.ParseFromAddr(x.conn.LocalAddr())
	gslog.Debug("[%d] Client session connect success, network: %s, local: %s, remote: %s", x.id, x.network, ip+":"+strconv.Itoa(int(port)), remote)
	if x.connectHandler != nil {
		x.connectHandler(x)
	}

	return nil
}

func (x *Client) write(data []byte) error {
	if x.writeTimeout > 0 {
		err := x.conn.SetWriteDeadline(time.Now().Add(time.Duration(x.writeTimeout) * time.Microsecond))
		if err != nil {
			return err
		}
	}

	x.dumpData("Write", data)
	_, err := x.conn.Write(data)
	return err
}

func (x *Client) Send(data []byte) {
	if !x.asyncSend {
		err := x.write(data)
		if err != nil {
			x.errorHandler(x, err)
			return
		}
	}

	x.writeMsgQueue <- data
}

func (x *Client) read() {
	for {
		var buf = make([]byte, x.readBuffer)
		read, err := x.conn.Read(buf[:])
		if err != nil {
			x.errorHandler(x, err)
			break
		}

		x.readMsgQueue <- buf[:read]
	}
}

func (x *Client) Close() {
	x.reConnect = false
	defer func() {
		x.cancel()
		gslog.Debug("[%d] Client session closed.", x.id)
		x.closeHandler(x)
		if x.conn != nil {
			x.conn.Close()
		}

		close(x.readMsgQueue)
		close(x.writeMsgQueue)
	}()

	if x.readIdleJob != nil {
		x.readIdleJob.Cancel()
	}

	if x.writeIdleJob != nil {
		x.writeIdleJob.Cancel()
	}

	if x.idleJob != nil {
		x.idleJob.Cancel()
	}
}

func (x *Client) Start() {
	err := x.connect()
	if err != nil {
		x.errorHandler(x, err)
		return
	}

	go x.pushJob()
	go x.read()

	// 处理读消息队列
	go x.processReadQueue()

	if x.asyncSend {
		// 处理写消息队列
		go x.processWriteQueue()
	}
}

func (x *Client) processReadQueue() {
	for msg := range x.readMsgQueue {
		x.dumpData("OnMessage", msg)
		now := time.Now()
		x.lastReadAt = now
		x.lastActiveAt = now
		if x.onMessage != nil {
			x.onMessage(x, msg)
		}
	}
}

func (x *Client) processWriteQueue() {
	for msg := range x.writeMsgQueue {
		x.lastWriteAt = time.Now()
		x.lastActiveAt = time.Now()
		err := x.write(msg)
		if err != nil {
			x.errorHandler(x, err)
			continue
		}
	}
}

func (x *Client) onIdleTimeout() {
	gslog.Trace("[%d] onIdleTimeout", x.id)
	if x.idleTimeoutHandler != nil {
		x.idleTimeoutHandler(x)
	}
}

func (x *Client) onWriteIdleTimeout() {
	gslog.Trace("[%d] onWriteIdleTimeout", x.id)
	if x.writeIdleTimeoutHandler != nil {
		x.writeIdleTimeoutHandler(x)
	}
}

func (x *Client) onReadIdleTimeout() {
	gslog.Trace("onReadIdleTimeout")
	if x.readIdleTimeoutHandler != nil {
		x.readIdleTimeoutHandler(x)
	}
}

func (x *Client) pushJob() {
	if x.idleTimeout > 0 {
		x.idleJob = NewClientJob(NewClientIdleTimeout(x), "clientIdleJob", &gsjob.ScheduleConfig{Every: x.idleTimeout})
		x.idleJob.Submit()
	}

	if x.readIdleTimeout > 0 {
		x.readIdleJob = NewClientJob(NewClientReadIdleTimeout(x), "clientReadIdleJob", &gsjob.ScheduleConfig{Every: x.readIdleTimeout})
		x.readIdleJob.Submit()
	}

	if x.writeIdleTimeout > 0 {
		x.writeIdleJob = NewClientJob(NewClientWriteIdleTimeout(x), "clientWriteIdleJob", &gsjob.ScheduleConfig{Every: x.writeIdleTimeout})
		x.writeIdleJob.Submit()
	}
}

func (x *Client) dumpData(label string, data []byte) {
	if gslog.GetLogLevel() != gslog.LEVEL_TRACE {
		return
	}

	gslog.Trace("[%d] %s-> length:%d \r\n%s", x.id, label, len(data), hex.Dump(data))
}

func (x *Client) GetConn() net.Conn {
	return x.conn
}
